#include "deinterleave_io.h"
#include "logger.h"
#include "samples_preprocessor.h"

CDeinterleaveIO::CDeinterleaveIO(ISampleIO *pIO)
		:mIO(pIO),
		mReadInterleavedData(NULL),
		mReadInterleavedLength(0),
		mReadInterleavedAction(UNKNOWN_ACTION),
		mReadByteBuf(NULL),
		mReadByteCount(0),
		mWriteInterleavedData(NULL),
		mWriteInterleavedLength(0),
		mWriteLastAction(UNKNOWN_ACTION),
		mWriteByteBuf(NULL),
		mWriteByteCount(0)
{
}

CDeinterleaveIO::~CDeinterleaveIO()
{
	if(mIO) {
		delete mIO;
	}
	getReadByteBuffer(0); // free the intern byte buffer
}

int CDeinterleaveIO::init(CPropertiesReader *pConfig)
{
	bool lInitError(false);
	if(mIO) {
		if(mIO->init(pConfig) != 0) {
			lInitError = true;
		}
	}
	if(lInitError) {
		return -1;
	}
	return 0;
}

int CDeinterleaveIO::destroy()
{
	bool lDestroyError(false);
	if(mIO) {
		if(mIO->destroy() != 0) {
			lDestroyError = true;
		}
	}
	if(lDestroyError) {
		return -1;
	}
	return 0;
}

int CDeinterleaveIO::read(void** pData, EAction& pAction, int pTimeout)
{
	if(!mIO) {
		return 0;
	}
	int lLength(0);
	if(!mReadInterleavedData) {
		// no data exist -> read is necessary

		mReadInterleavedLength = mIO->read(&mReadInterleavedData, mReadInterleavedAction, pTimeout);
		if(mReadInterleavedAction != INTERLEAVED_EMBED_EXTRACT) {
			printl(LL_ERR, __FILE__, "No interleaved data from read.\n");
			return 0;
		}

		// get embedding data
		void* lData = getReadByteBuffer(mReadInterleavedLength / 2);
		lLength = deinterleave(lData,
				mReadInterleavedLength / 2,
				mReadInterleavedData,
				mReadInterleavedLength,
				true);
		*pData = lData;
		pAction = EMBED;
	}
	else {
		// data available -> no read is necessary

		if(mReadInterleavedAction != INTERLEAVED_EMBED_EXTRACT) {
			printl(LL_ERR, __FILE__, "No interleaved data from read.\n");
			return 0;
		}

		// embedding data already used.
		// now get extracting data
		void* lData = getReadByteBuffer(mReadInterleavedLength / 2);
		lLength = deinterleave(lData,
				mReadInterleavedLength / 2,
				mReadInterleavedData,
				mReadInterleavedLength,
				false);
		*pData = lData;
		pAction = EXTRACT;

		mReadInterleavedData = NULL;
		mReadInterleavedLength = 0;
		mReadInterleavedAction = UNKNOWN_ACTION;
	}
	return lLength;
}

int CDeinterleaveIO::write(void* pData, const EAction& pAction, int pLength)
{
	if(!mIO) {
		return 0;
	}
	if(pAction == mWriteLastAction) {
		printl(LL_ERR, __FILE__, "Wrong action. Must be EMBED or EXTRACT.\n");
		return 0;
	}
	bool lReadyForWrite(true);
	if(!mWriteInterleavedData) {
		mWriteInterleavedLength = pLength * 2;
		mWriteInterleavedData = getWriteByteBuffer(mWriteInterleavedLength);
		lReadyForWrite = false;
	}

	switch(pAction) {
		case EMBED:
			if(interleave(mWriteInterleavedData,
					mWriteInterleavedLength,
					pData,
					pLength,
					true) != mWriteInterleavedLength) {
				printl(LL_ERR, __FILE__, "interleave methode for EMBED failed.\n");
				return 0;
			}
			break;
		case EXTRACT:
			if(interleave(mWriteInterleavedData,
					mWriteInterleavedLength,
					pData,
					pLength,
					false) != mWriteInterleavedLength) {
				printl(LL_ERR, __FILE__, "interleave methode for EXTRACT failed.\n");
				return 0;
			}
			break;
		default:
			printl(LL_ERR, __FILE__, "Action not allowed.\n");
			return 0;
	}

	if(lReadyForWrite) {
		EAction lInterleavedAction(INTERLEAVED_EMBED_EXTRACT);
		if(mIO->write(mWriteInterleavedData,
				lInterleavedAction,
				mWriteInterleavedLength) != mWriteInterleavedLength) {
			printl(LL_ERR, __FILE__, "Interleaved write failed.\n");
			return 0;
		}
		mWriteInterleavedData = NULL;
		mWriteInterleavedLength = 0;
		mWriteLastAction = UNKNOWN_ACTION;
	}
	return pLength;
}

NAudioFormats::SStreamInfo CDeinterleaveIO::getStreamInfo()
{
	if(mIO) {
		return mIO->getStreamInfo();
	}
	printl(LL_WARN, __FILE__, "Currently no stream infos available.\n");
	return NAudioFormats::SStreamInfo(NAudioFormats::UNKNOWN_WIDTH,
			0,
			NAudioFormats::UNKNOWN_CODEC);
}

void* CDeinterleaveIO::getReadByteBuffer(int pByteLength)
{
	if((!mReadByteBuf && mReadByteCount > 0)
	|| (mReadByteBuf && mReadByteCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent read byte buffer\n");
		return NULL;
	}
	if(pByteLength <= 0) {
		if(mReadByteBuf) {
			delete [] mReadByteBuf;
			mReadByteBuf = NULL;
			mReadByteCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pByteLength > mReadByteCount) {
		delete [] mReadByteBuf;
		mReadByteBuf = new char [pByteLength];
		mReadByteCount = pByteLength;
	}
	return mReadByteBuf;
}

void* CDeinterleaveIO::getWriteByteBuffer(int pByteLength)
{
	if((!mWriteByteBuf && mWriteByteCount > 0)
	|| (mWriteByteBuf && mWriteByteCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent write byte buffer\n");
		return NULL;
	}
	if(pByteLength <= 0) {
		if(mWriteByteBuf) {
			delete [] mWriteByteBuf;
			mWriteByteBuf = NULL;
			mWriteByteCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pByteLength > mWriteByteCount) {
		delete [] mWriteByteBuf;
		mWriteByteBuf = new char [pByteLength];
		mWriteByteCount = pByteLength;
	}
	return mWriteByteBuf;
}

int CDeinterleaveIO::deinterleave
(
		void* pOutBuffer,
		int pOutLength,
		void* pInBuffer,
		int pInLength,
		bool pEmbedSamples
)
{
	if(!pOutBuffer || !pOutLength || !pInBuffer || !pInLength) {
		printl(LL_ERR, __FILE__, "NULL pointer or a length of 0.\n");
		return 0;
	}
	int lBytePerSample = CSamplesPreprocessor::getBytePerSample(mIO->getStreamInfo().mSamplesWidth);
	int lBytePerFrame(lBytePerSample * 2);
	if(!lBytePerSample) {
		printl(LL_ERR, __FILE__, "Byte per sample can't be 0.\n");
		return 0;
	}
	if(pInLength % lBytePerSample) {
		printl(LL_ERR, __FILE__, "Buffer is not a multiple of byte per sample.\n");
		return 0;
	}
	if(pInLength % lBytePerFrame) {
		printl(LL_ERR, __FILE__, "Buffer is not a multiple of a frame"
				" (frame = embed + extract sample)\n");
		return 0;
	}
	if(pOutLength < (pInLength / 2)) {
		printl(LL_ERR, __FILE__, "Output buffer is to small.\n");
		return 0;
	}
	char *pOutByteBuf(static_cast<char*>(pOutBuffer));
	char *pInByteBuf(static_cast<char*>(pInBuffer));
	if(!pEmbedSamples) {
		// use extract samples -> move from embed to extract samples
		pInByteBuf += lBytePerSample;
	}
	for(int i = 0; i < (pInLength / lBytePerFrame); i++) {
		for(int iSampleCopy = 0; iSampleCopy < lBytePerSample; iSampleCopy++) {
			pOutByteBuf[iSampleCopy] = pInByteBuf[iSampleCopy];
		}
		pOutByteBuf += lBytePerSample;
		pInByteBuf += lBytePerFrame;
	}
	return pInLength / 2;
}

int CDeinterleaveIO::interleave
(
		void* pOutBuffer,
		int pOutLength,
		void* pInBuffer,
		int pInLength,
		bool pEmbedSamples
)
{
	if(!pOutBuffer || !pOutLength || !pInBuffer || !pInLength) {
		printl(LL_ERR, __FILE__, "NULL pointer or a length of 0.\n");
		return 0;
	}
	int lBytePerSample = CSamplesPreprocessor::getBytePerSample(mIO->getStreamInfo().mSamplesWidth);
	//int lBytePerFrame(lBytePerSample * 2);
	if(!lBytePerSample) {
		printl(LL_ERR, __FILE__, "Byte per sample can't be 0.\n");
		return 0;
	}
	if(pInLength % lBytePerSample) {
		printl(LL_ERR, __FILE__, "Buffer is not a multiple of byte per sample.\n");
		return 0;
	}
	/*
	if(pInLength % lBytePerFrame) {
		printl(LL_ERR, __FILE__, "Buffer is not a multiple of a frame"
				" (frame = embed + extract sample)\n");
		return 0;
	}*/
	if(pOutLength < (pInLength * 2)) {
		printl(LL_ERR, __FILE__, "Output buffer is to small.\n");
		return 0;
	}
	char *pOutByteBuf(static_cast<char*>(pOutBuffer));
	char *pInByteBuf(static_cast<char*>(pInBuffer));
	if(!pEmbedSamples) {
		// use extract samples -> move from embed to extract samples
		pOutByteBuf += lBytePerSample;
	}
	for(int i = 0; i < (pInLength / lBytePerSample); i++) {
		for(int iSampleCopy = 0; iSampleCopy < lBytePerSample; iSampleCopy++) {
			pOutByteBuf[iSampleCopy] = pInByteBuf[iSampleCopy];
		}
		pOutByteBuf += (lBytePerSample * 2);
		pInByteBuf += lBytePerSample;
	}
	return pInLength * 2;
}
